/* Copyright 2001,2002,2003 NAH6 BV
 * All Rights Reserved
 *
 *  $Header: /var/lib/cvs/secphone/ui/phone/spif.cpp,v 1.11 2003/11/21 16:12:58 itsme Exp $
 *
 *
 *
 * this module handles the low level interface to spipc.dll
 *
 * todo: - merge this module with spipchandler.cpp
 *
 */

#include <windows.h>
#include "spif.h"
#include "debug.h"
#include "PhoneOptions.h"

#include <string>
#include <algorithm>
#include "spipc.h"
#include "stringutils.h"

#define PTR_DIFF(a,b)  (((BYTE*)b)-((BYTE*)a))

#ifndef _WIN32_WCE
  #define QUEUE_RD_PRIO_WIN  (THREAD_PRIORITY_TIME_CRITICAL)
#else
  #define QUEUE_RD_PRIO_WIN  (247)
#endif

static int SetThreadPriority(int newprio)
{
    int prio;

    HANDLE thnd = GetCurrentThread ();
#ifndef _WIN32_WCE
    prio = GetThreadPriority (thnd);
    SetThreadPriority (thnd, newprio);
#else
    prio = CeGetThreadPriority (thnd);
    CeSetThreadPriority (thnd, newprio);
#endif

    return prio;
}

// lowlevel functions first

dword g_cmdid= 0;
int g_current_phone= DEFAULT_PHONE;

// sends a systemwide command.
int SP__sendSystemCmd( dword dwId, word nCmd, char *fmt, ...)
{
    ByteVector buf;

    if (fmt) {
        va_list ap;
        va_start(ap, fmt);
        vbufpack(buf, fmt, ap);
        va_end(ap);
    }

    ByteVector hdr;
#ifdef _DEBUG_IPCMESSAGES
    bufpack(hdr, "vvVVB", sizeof(SPIPC_HEADER)+buf.size(), nCmd, dwId, GetTickCount(), &buf);
#else
    bufpack(hdr, "vvVB", sizeof(SPIPC_HEADER)+buf.size(), nCmd, dwId, &buf);
#endif

    int rc= SPIPC_putMessage( K_SPIPC_QUEUE_COMMAND, vectorptr(hdr), 0 );

    debug(">>> %3d %hs : %hs\n", dwId, SPIPCCommandString(nCmd), SPIPCResult(rc));

    return rc;
}

// sends a phoneline specific command
int SP__sendPhoneCmd( dword dwId, word nCmd, char *fmt, ...)
{
    ByteVector buf;

    if (fmt) {
        va_list ap;
        va_start(ap, fmt);
        vbufpack(buf, fmt, ap);
        va_end(ap);
    }

    ByteVector hdr;
#ifdef _DEBUG_IPCMESSAGES
    bufpack(hdr, "vvVVCB", sizeof(SPIPC_HEADER)+1+buf.size(), nCmd, dwId, GetTickCount(), g_current_phone, &buf);
#else
    bufpack(hdr, "vvVCB", sizeof(SPIPC_HEADER)+1+buf.size(), nCmd, dwId, g_current_phone, &buf);
#endif

    int rc= SPIPC_putMessage( K_SPIPC_QUEUE_COMMAND, vectorptr(hdr), 0 );

    debug(">>> %3d %hs : %hs\n", dwId, SPIPCCommandString(nCmd), SPIPCResult(rc));

    return rc;
}

// retrieve a message, called after a notification was received
int SP__getMessage( word *pnCmd, dword *pdwId, word *pnDataLen, void **pData )
{
    *pData= 0;

    int origprio= SetThreadPriority(QUEUE_RD_PRIO_WIN);
    int res= SPIPC_getMessage( K_SPIPC_QUEUE_RESULT, NULL, pnDataLen, 0 );
    SetThreadPriority(origprio);
    if( res!=E_SPIPC_OK && res!=E_SPIPC_STRUCT2SMALL )
        return res;

    *pData= LocalAlloc( LPTR, *pnDataLen );
    if( *pData==NULL )
        return E_SPIPC_UNKNOWN;

    origprio= SetThreadPriority(QUEUE_RD_PRIO_WIN);
    res= SPIPC_getMessage( K_SPIPC_QUEUE_RESULT, (BYTE*)*pData, pnDataLen, 0 );
    SetThreadPriority(origprio);
    if( res!=E_SPIPC_OK )
    {
        // make sure to not return data in case of error...
        LocalFree( *pData );
        *pData= 0;
        return res;
    }

    *pnCmd= ((SPIPC_HEADER*)*pData)->msg;
    *pdwId= ((SPIPC_HEADER*)*pData)->id;

    if( *pnDataLen==sizeof( SPIPC_HEADER ) )
    {
        // no need to return header only
        LocalFree( *pData );
        *pnDataLen= 0;
        *pData= 0;
    }

    return res;
}
void SP_ClearBuffer()
{
    word nCmd;
    dword dwId;
    word len;
    void *pData;

    debug("SP: clearing buffer\n");
    while (E_SPIPC_OK==SP__getMessage(&nCmd, &dwId, &len, &pData))
    {
        debug("IGNORING <<< %3d %hs\n", dwId, SPIPCCommandString(nCmd));
        if( pData )
            LocalFree( pData );
    }
    debug("SP: buffer cleared\n");
}

// high level functions
std::wstring GetAppPath()
{
    std::wstring appname;
    appname.resize(MAX_PATH);
    if (!GetModuleFileName(NULL, stringptr(appname), appname.size()))
        return NULL;
    appname.resize(_tcslen(stringptr(appname)));

    return appname;
}

bool FindAndStartMachine()
{
    TCHAR machine_cmdline[]= L"SPcore.exe --silent";
    PROCESS_INFORMATION procinfo;

    STARTUPINFO startinfo;
    memset(&startinfo, 0, sizeof(STARTUPINFO));
    startinfo.cb= sizeof(STARTUPINFO);

    // first try from the search PATH
    if (CreateProcess(L"spcore.exe", machine_cmdline, NULL, NULL,
        FALSE, 0, NULL, NULL, &startinfo, &procinfo))
    {
        debug("FindAndStartMachine: spcore.exe\n");
        CloseHandle (procinfo.hThread);
        CloseHandle (procinfo.hProcess);
        return true;
    }

    // then try from the configured path to CryptoPhone.exe
    std::wstring alternative_path= g_options.exepath;
    size_t slashpos= alternative_path.find_last_of(L"\\/");
    if (slashpos!=alternative_path.npos) {
        alternative_path.resize(slashpos);
        alternative_path += L"\\spcore.exe";

        if (CreateProcess(alternative_path.c_str(), machine_cmdline, NULL, NULL,
                FALSE, 0, NULL, NULL, &startinfo, &procinfo)) {
            debug("FindAndStartMachine: %hs\n", alternative_path.c_str());
            CloseHandle (procinfo.hThread);
            CloseHandle (procinfo.hProcess);
            return true;
        }
    }
    // then try where ever we came from
    alternative_path= GetAppPath();
    alternative_path.resize(alternative_path.find_last_of('\\'));
    alternative_path += L"\\spcore.exe";
    if (CreateProcess(alternative_path.c_str(), machine_cmdline, NULL, NULL,
                FALSE, 0, NULL, NULL, &startinfo, &procinfo)) {
        debug("FindAndStartMachine: %hs\n", alternative_path.c_str());
        CloseHandle (procinfo.hThread);
        CloseHandle (procinfo.hProcess);
        return true;
    }
    error("CreateProcess('spcore.exe')");
    return false;
}

bool MachineIsRunning()
{
    HANDLE elck;
    DWORD e;
    TCHAR machine_event[]  = L"SPCORE";

    // Create event-lock
    elck = CreateEvent (NULL, TRUE, 0, machine_event);
    if (elck == NULL) {
        error("MachineIsRunning: createevent\n");
        return false;
    }

    // Got something...
    e = GetLastError ();
    CloseHandle(elck);
    if (e == ERROR_ALREADY_EXISTS)
        return true;

    debug("MachineIsRunning: machine not found\n");
    return false;
}

int SP_initSystem(HWND hNotifyWnd)
{
    SPIPC_VERSIONINFO ipcver;
    int res;

    // Setup ipc-dll
    res = SPIPC_getVersionInfo (&ipcver);
    if (res != E_SPIPC_OK)
        return res;
    
    debug("SPIPC v%d.%d  opt=%04x, %d queues, shared memory %08lx  dll=%08lx\n", 
        ipcver.ifver, ipcver.ifrev, ipcver.ifopt, ipcver.queues,
        ipcver.data, ipcver.code);

    if (ipcver.magic != K_SPIPC_MAGIC)
        return E_SPIPC_UNKNOWN;

    res = SPIPC_setNotifyWnd (K_SPIPC_QUEUE_RESULT, K_SPIPC_NOTIFY_RXRDY, hNotifyWnd);
    if (res != E_SPIPC_OK)
        return res;

    if (!MachineIsRunning()
        && !FindAndStartMachine())
        return E_SPIPC_UNKNOWN;
    
    SP_ClearBuffer();

    return E_SPIPC_OK;
}


int SP_initPhone(int cfgtype)
{
    int res;

    g_current_phone= 0;
    // turn off & configure the machine
    res= SP__sendPhoneCmd( ++g_cmdid, SPIPC_CMD_OFF );
    if( res!=E_SPIPC_OK )
        return res;
    
    res= SP__sendPhoneCmd( ++g_cmdid, SPIPC_CMD_AUTOCFG, "CCC", 
            cfgtype, g_options.modem_port, g_options.modem_type);
    if( res!=E_SPIPC_OK )
        return res;

    // and turn on...
    res= SP__sendPhoneCmd( ++g_cmdid, SPIPC_CMD_ON );
    if ( res!=E_SPIPC_OK )
        return res;

    return E_SPIPC_OK;
}

int SP_stopSystem()
{
    int res= SP__sendSystemCmd( ++g_cmdid, SPIPC_CMD_EXIT );

    if ( res!=E_SPIPC_OK )
        return res;

    return res;
}

int SP_dial_gsm( byte nCarrier, const std::string& number)
{
    return SP__sendPhoneCmd(++g_cmdid, SPIPC_CMD_DIAL, "CCCCS", 
            SPIPC_LINKMEDIA_TEL, SPIPC_LINKSERVICE_DATA, nCarrier, SPIPC_LINKPROTOCOL_RAW, &number);
}

int SP_hangup( )
{
    return SP__sendPhoneCmd( ++g_cmdid, SPIPC_CMD_HANGUP );
}

int SP_answer()
{
    return SP__sendPhoneCmd( ++g_cmdid, SPIPC_CMD_ANSWER );
}

int SP_ping( )
{
    int res= SP__sendSystemCmd( ++g_cmdid, SPIPC_CMD_PING );
    if ( res!=E_SPIPC_OK )
        return res;

    return res;
}

std::string SP_getCallerId( const SPIPC_MSG_EVT_CALLERID* pData )
{
    return std::string((char*)pData->remnumdata, (char*)pData->remnumdata+pData->remnumlen);
}

short SP_getError( const SPIPC_MSG_RES_ERROR* pData )
{
    return pData->result;
}

int SP_sendUserData(const ByteVector& data)
{
    return SP__sendPhoneCmd(++g_cmdid, SPIPC_CMD_TXUSERDATA, "B", &data);
}

int SP_setDecoderKey(const ByteVector& key, bool bOriginating)
{
    ByteVector cnt; cnt.resize(16);
    if (!bOriginating)
        cnt[15] |= 0x80;

    return SP__sendPhoneCmd(++g_cmdid, SPIPC_CMD_SETDECSTATE, "BB", &key, &cnt);
}

int SP_setEncoderKey(const ByteVector& key, bool bOriginating)
{
    ByteVector cnt; cnt.resize(16);
    if (bOriginating)
        cnt[15] |= 0x80;

    return SP__sendPhoneCmd(++g_cmdid, SPIPC_CMD_SETENCSTATE, "BB", &key, &cnt);
}

int SP_setEncoderMode(int mode)
{
    return SP__sendPhoneCmd(++g_cmdid, SPIPC_CMD_SETENCMODE, "C", mode);
}

int SP_setDecoderMode(int mode)
{
    return SP__sendPhoneCmd(++g_cmdid, SPIPC_CMD_SETDECMODE, "C", mode);
}

int SP_requestCoderStates()
{
    int r1= SP__sendPhoneCmd( ++g_cmdid, SPIPC_CMD_GETDECSTATE );
    int r2= SP__sendPhoneCmd( ++g_cmdid, SPIPC_CMD_GETENCSTATE );

    return r1==E_SPIPC_OK ? r2 : r1;
}

int SP_setVolume(int volume)
{
    return SP__sendPhoneCmd( ++g_cmdid, SPIPC_CMD_SETOUTVOL, "Cv", 0, volume);
}
int SP_getVolume()
{
    return SP__sendPhoneCmd( ++g_cmdid, SPIPC_CMD_GETOUTVOL, "C", 0);
}
